#include "Alice/Resource/Shader.h"
#include "Alice/Log/Log.h"

namespace Alice
{

Shader& Shader::Use()
{
    glUseProgram(Id);
    return *this;
}

void Shader::Compile(
        const char* vertex_source, 
        const char* fragment_source,
        const char* geometry_source = nullptr
    )
{
    unsigned int vertex_shader, fragment_shader, geometry_shader;
    // 创建顶点着色器
    vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex_shader, 1, &vertex_source, NULL);
    glCompileShader(vertex_shader);
    CheckCompileErrors(vertex_shader, "VERTEX");
    // 片段着色器
    fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader, 1, &fragment_source, NULL);
    glCompileShader(fragment_shader);
    CheckCompileErrors(fragment_shader, "FRAGMENT");
    // 几何着色器
    if (geometry_source != nullptr)
    {
        geometry_shader = glCreateShader(GL_GEOMETRY_SHADER);
        glShaderSource(geometry_shader, 1, &geometry_source, NULL);
        glCompileShader(geometry_shader);
        CheckCompileErrors(geometry_shader, "GEOMETRY");
    }
    // 链接着色器程序
    Id = glCreateProgram();
    glAttachShader(Id, vertex_shader);
    glAttachShader(Id, fragment_shader);
    if (geometry_source != nullptr)
    {
        glAttachShader(Id, geometry_shader);
    }
    glLinkProgram(Id);
    CheckCompileErrors(Id, "PROGRAM");
    // 链接完成清理着色器
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);
    if (geometry_source != nullptr)
    {
        glDeleteShader(geometry_shader);
    }
}

void Shader::SetFloat(const char* name, float value, bool use_shader)
{
    if (use_shader)
        Use();
    glUniform1f(glGetUniformLocation(Id, name), value);
}
void Shader::SetInt(const char* name, int value, bool use_shader)
{
    if (use_shader)
        Use();
    glUniform1i(glGetUniformLocation(Id, name), value);
}
void Shader::SetVec2f(const char* name, float x, float y, bool use_shader)
{
    if (use_shader)
        Use();
    glUniform2f(glGetUniformLocation(Id, name), x, y);
}
void Shader::SetVec2f(const char* name, const glm::vec2& value, bool use_shader)
{
    if (use_shader)
        Use();
    glUniform2f(glGetUniformLocation(Id, name), value.x, value.y);
}
void Shader::SetVec3f(const char* name, float x, float y, float z, bool use_shader)
{
    if (use_shader)
        Use();
    glUniform3f(glGetUniformLocation(Id, name), x, y, z);
}
void Shader::SetVec3f(const char* name, const glm::vec3& value, bool use_shader)
{
    if (use_shader)
        Use();
    glUniform3f(glGetUniformLocation(Id, name), value.x, value.y, value.z);
}
void Shader::SetVec4f(const char* name, float x, float y, float z, float w, bool use_shader)
{
    if (use_shader)
        Use();
    glUniform4f(glGetUniformLocation(Id, name), x, y, z, w);
}
void Shader::SetVec4f(const char* name, const glm::vec4& value, bool use_shader)
{
    if (use_shader)
        Use();
    glUniform4f(glGetUniformLocation(Id, name), value.x, value.y, value.z, value.w);
}
void Shader::SetMat44(const char* name, const glm::mat4& matrix, bool use_shader)
{
    if (use_shader)
        Use();
    glUniformMatrix4fv(glGetUniformLocation(Id, name), 1, GL_FALSE, glm::value_ptr(matrix));
}

void Shader::CheckCompileErrors(unsigned int object, std::string type)
{
    int success;
    char info_log[1024];

    if (type != "PROGRAM")
    {
        glGetShaderiv(object, GL_COMPILE_STATUS, &success);
        if (!success)
        {
            glGetShaderInfoLog(object, 1024, NULL, info_log);
            ALICE_ERROR("Shader: Compile {} Error: type: {}", type, info_log);
        }
    }
    else
    {
        glGetProgramiv(object, GL_LINK_STATUS, &success);
        if (!success)
        {
            glGetProgramInfoLog(object, 1024, NULL, info_log);
            ALICE_ERROR("Shader: Link {} Error: type: {}", type, info_log);
        }
    }
}

} // namespace Alice
